// -----------------------------------------------------------------------------
//
// GAMCS -- Generalized Agent Model and Computer Simulation
//
// Copyright (C) 2013-2014, Andy Huang  <andyspider@126.com>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// -----------------------------------------------------------------------------

#include <random>
#include <stdlib.h>
#include <cmath>
#include "gamcs/debug.h"
#include "gamcs/GIOM.h"

namespace gamcs
{

/**
 * @brief The default constructor.
 *
 * Initialize the random device.
 */
GIOM::GIOM() :
		cur_in(INVALID_INPUT), cur_out(INVALID_OUTPUT), process_count(0), rand_device(
		NULL), max_rand_value(0)
{
	rand_device = new std::random_device();    // to get true random on linux, use rand("/dev/random") instead;
	max_rand_value = rand_device->max();    // save the maximum value
}

/**
 * @brief The default destructor.
 *
 * Destroy the random device.
 */
GIOM::~GIOM()
{
	delete rand_device;
}

/**
 * @brief Constraining capacity of the GIOM.
 *
 * Minimum constrain by default, which means NO constrain at all here.
 * @param [in] input the input value
 * @param [in] outputs the output space for current input
 * @return the sub output space after constraining
 */
OSpace GIOM::constrain(Input input, OSpace &outputs) const
{
	UNUSED(input);
	return outputs;    // no constraint at all
}

/**
 * @brief The process function of GIOM.
 *
 * Return a random item from the output space by default.
 * @param [in] input the input value
 * @param [in] outputs the output space of the input
 * @return the generated output
 */
GIOM::Output GIOM::process(Input input, OSpace &outputs)
{
	OSpace restricited_outputs = constrain(input, outputs);    // get the constrained sub output space first
	if (restricited_outputs.empty())    // if no output generated, return an invalid output
		return INVALID_OUTPUT;

	gamcs_uint sz = restricited_outputs.size();    // size of the subspace
	gamcs_uint index = randomGenerator(sz) % (sz);    // choose an output randomly
	GIOM::Output out = restricited_outputs[index];

	// record the input and output
	cur_in = input;
	cur_out = out;
	++process_count;    // increase the count
	return out;
}

/**
 * @brief Calculate the entropy of a state under constraining.
 *
 * @param [in] input the specified input
 * @param [in] outputs the output space of the input
 * @return the entropy value
 */
float GIOM::singleOutputEntropy(Input input, OSpace &outputs) const
{
	OSpace restricted_outputs = constrain(input, outputs);
	if (restricted_outputs.empty())
		return 0.0;

	gamcs_uint sz = restricted_outputs.size();
	return pi_log2((double) sz);    // all the alpos_outputs have the same probability of occurrence
}

/**
 * @brief Update inner data of GIOM and prepare for the next processing.
 *
 * Derived classes may have their own stuff to be updated.
 */
void GIOM::update()
{
	// clear input and output for the next processing
	cur_in = INVALID_INPUT;
	cur_out = INVALID_OUTPUT;
	return;
}

/**
 * @brief Generate a random number in the specified range.
 *
 * The random is where all possibilities and miracles come from.
 * @param [in] sz the specified range
 * @return the generated random number
 */
gamcs_uint GIOM::randomGenerator(gamcs_uint sz) const
{
	std::uniform_int_distribution<gamcs_uint> dist(0, sz - 1);    // random number range: 0 ~ sz-1

	// check upper bound
	if (max_rand_value < sz - 1)
		WARNNING(
				"size exceeds maximun random value potentially generated by the random-number engine\n");
	return dist(*rand_device);
}

}    // namespace gamcs
